#pragma once

#include "net_common.hpp"
#include "net_tsdeque.hpp"
#include "net_message.hpp"
#include "net_connection.hpp"

namespace dw
{
    namespace net
    {
        template <typename T>
        class client_interface
        {
        private:
            // handles messages received from server
            // remote should be nullptr
            // because the server is unique to the client
            tsdeque<owned_message<T>> m_qMsgIn;

        protected:
            // asio context handles the data transfer
            asio::io_context m_context;
            // ... but needs a thread of its own to execute its work commands
            std::thread thrContext;

            // This is the hardware socket that is connected to the server
            asio::ip::tcp::socket m_socket;
            // A client has a single instance of a "connection" objects
            std::unique_ptr<connection<T>> m_connection;

        public:
            client_interface() : m_socket(m_context)
            {
                // Initialize the socket with the client's own io_context,
                // so it can handle tcp stuff

                // done
            }

            ~client_interface()
            {
                Disconnect();
            }

        public:
            bool Connect(const std::string &host, const uint16_t port)
            {
                try
                {
                    // Resolve hostname/ip-address into tangiable physical address
                    asio::ip::tcp::resolver resolver(m_context);
                    asio::ip::tcp::resolver::results_type endpoints = resolver.resolve(host, std::to_string(port));

                    // Create connection
                    m_connection = std::make_unique<connection<T>>(
                        connection<T>::owner::client,
                        m_context,
                        asio::ip::tcp::socket(m_context),
                        m_qMsgIn
                    );

                    m_connection->ConnectToServer(endpoints);

                    thrContext = std::thread([this]() { m_context.run(); });
                }
                catch(const std::exception& e)
                {
                    std::cerr << "Client Exception: " << e.what() << std::endl;
                    return false;
                }
                
                return true;
            }

            void Disconnect()
            {
                if (IsConnected())
                {
                    m_connection->Disconnect();
                }

                // Either way, we're also done with the asio context...
                m_context.stop();
                // ...and its thread
                if (thrContext.joinable())
                {
                    thrContext.join();
                }

                // ...and also done with the unique_ptr
                // reset() will call deconstructor while release() won't
                m_connection.reset();
            }

            bool IsConnected()
            {
                if (m_connection)
                {
                    return m_connection->IsConnected();
                }
                else{
                    return false;
                }
            }

            void Send(const message<T> &msg)
            {
                if (m_connection->IsConnected())
                {
                    m_connection->Send(msg);
                }
            }

        public:
            tsdeque<owned_message<T>> &Incomming()
            {
                return m_qMsgIn;
            }
        };
    }
}